package simulacao;

// AgenteBot

// Java AgentBot.
// Autor: Alexandre Heideker - alexandre.heideker@ufabc.edu.br
// UFABC Jun/2009
//

import java.util.ArrayList;
import java.util.Collections;
import swarm.space.Grid2d;
import swarm.gui.Raster;

/**
 AgenteBot

<p>
A Heatbug is an agent in a 2-dimensional world. A Heatbug has the following
behavior:

 <dir>
 Each Heatbug has its own ideal temperature, and a color that indicates the 
 Heatbug's ideal temperature (more green for cool-loving Heatbugs, more yellow 
 for warmth-loving Heatbugs).
 </dir>

 <dir>
 O AgenteBot Ã© uma simulaÃ§Ã£o de robÃ´s em mapas desconhecidos utilizando
 algoritmos de busca para aprendizado.
 </dir>

 <dir>
 Agentes:
	- ObstÃ¡culo (mapa)
		posiÃ§Ã£o X,Y

	- Robo:
		posiÃ§ao x,y
		Nome - nome do agente
		TempoDoTrabalho - registra o tempo necessÃ¡rio para encontrar o alvo
		GridMemoriaPessoal - registra a BC obtida pelo agente
		GridMemÃ³riaPÃºblica - registra a BC obtida em trocas
		Target - Objetivo escolhido entre os disponÃ­veis
		AdicionaElementoBC - mÃ©todo para processar a BC obtida em interaÃ§Ãµes
		tipo:
			- Robo AMIGO
				Fornece BC - troca informaÃ§Ãµes com outros robÃ´s (passam sua base de conhecimento)
				Absorve BC - adiciona base de conhecimento obtida com robÃ´s amigos

			- Robo NÃ£o Amigo
				Fornece Dirt BC - Fornece BC falsa a fim de confundir AMIGO
	- Objetivo
		posiÃ§Ã£o x,y
		nome

Base de conhecimento:
	- Grid 2d com os elementos encontrados em cada contato.

Forma de carregar um mapa.....
ApÃ³s o mapa ser carregado, os objetivos sÃ£o e os agentes. Cada agente recebe aleatoriamente a missÃ£o
de encontrar um objeto.
A simulaÃ§Ã£o tem inicio e para quando todos os agentes robÃ´ alcanÃ§aram seu objetivo.

 </dir>

*/
public class Bot
{
    // Coordenas X,Y do bot:
    public int x, y;
    // Tempo para encontrar o alvo:
    public int tempo;

    public String nome="";

    // armazena o ultimo movimento realizado
    private int ultimoMovimento=4;
    // flag que indica que o robot deve buscar outro caminho

    public byte colorIndex;

    private Grid2d _world;

    private Memoria _memo;

    private BotModelSwarm _model;

public Bot (Grid2d world, BotModelSwarm model,
  int BotIndex, Memoria memoria )
{
    _world = world;
    _model = model;
    _memo=memoria;

    if (_world == null)
        System.err.println ("O Bot nÃ£o tem um mundo!");
} /// constructor

public Object drawSelfOn (Raster raster)
{
    raster.drawPointX$Y$Color (x, y, colorIndex);
    return this;
}

private class Coordenada implements Comparable<Coordenada>{
    public int x;
    public int y;
    public int nota;
    public double distancia;
    public int indice=4; //utilizado em A*
    public boolean vizitado=false; //utilizado em A*
    //public boolean possivel=false; //utilizado em A*
    public Coordenada(int x, int y){
        this.x=x;
        this.y=y;
        this.nota=1;
        this.distancia=100000;
    }
    public Coordenada(){
        this.nota=1;
        this.distancia=100000;
    }
    public void setXY(int x, int y){
        this.x=x;
        this.y=y;
        if (x<0||x>=_world.getSizeX()||y<0||y>=_world.getSizeY())
            this.nota=0;
    }
    public void setXY(int x, int y, int i){
        setXY(x,y);
        this.indice=i;
    }
    public void setDistancia(int xFim, int yFim){
        if (_memo.getValueAtX$Y(this.x, this.y)>1&&this.nota>0)
            this.distancia=(double)Math.sqrt(Math.pow(Math.abs(this.x-xFim),2)+Math.pow(Math.abs(this.y-yFim),2));
        else
            //hÃ¡ um bloqueio ou Ã© uma Ã¡rea desconhecida....
            this.distancia=100000;

    }
    public boolean eIgual(Coordenada ponto){
        if (ponto.x==this.x&&ponto.y==this.y)
            return true;
        else
            return false;
    }
    public int compareTo(Coordenada ponto){
        if (ponto.distancia>this.distancia)
            return -1;
        else
            return 1;
    }
    public String toString(){
        return String.format("x=%d y=%d  nota=%d  indice=%d  distancia=%f ", this.x,this.y,this.nota,this.indice, this.distancia);
    }
}


public synchronized void BotStep ()
{

    int newX, newY;

    Coordenada xy;

    xy=Decisao();

    if (xy.nota>0){
        if (_memo.getValueAtX$Y(xy.x, xy.y)<14) //incrementa o nÃºmero de passagens pelo local
            _memo.putValue$atX$Y(_memo.getValueAtX$Y(xy.x, xy.y)+1, xy.x, xy.y);
        _world.putObject$atX$Y (null, this.x, this.y);
        this.x=xy.x;
        this.y=xy.y;
        _world.putObject$atX$Y (this, this.x, this.y);
    }
}


private Coordenada Decisao(){
    //este mÃ©todo escolhe entre as 4 possibilidades de movimento, o mais adequado
    //de acordo com as heurÃ­sticas escolhidas.
    //
    //

    Coordenada mv[]=new Coordenada[5];  //inicializa os caminho com nota 1 para cada um
                                        //Verifica os limites geogrÃ¡ficos dos possÃ­veis caminhos...
                                        //Caminhos fora dos limites geogrÃ¡ficos recebem nota 0
    for (int i=0;i<5;i++)
        mv[i]=new Coordenada();

    mv[0].setXY(x,y-1);
    mv[1].setXY(x,y+1);
    mv[2].setXY(x+1,y);
    mv[3].setXY(x-1,y);
    mv[4].setXY(x,y);
    /*
     * Primeiro passo Ã© verificar se o RobÃ´ jÃ¡ chegou ao objetivo
     */




    if (this.x==StartBot.saidaX&&this.y==StartBot.saidaY)
        //RobÃ´ jÃ¡ chegou na saÃ­da
        return mv[4];

    int escolha=5; //esta variÃ¡vel indica qual o caminho escolhido.

    // Esta simulaÃ§Ã£o opera de duas formas distintas:
    //  - busca cega mapeando o ambiente;
    //  - busca A* quando um dos agentes encontra a saÃ­da

    // a saÃ­da Ã© identificada quando uma das coordenadas possÃ­veis Ã©
    // igual a 0 ou igual a worldSize (jÃ¡ que o ambiente Ã© todo cercado)
    if (StartBot.saidaX>=0){
        //A saÃ­da foi encontrada....aplicar heurÃ­stica A*
        //Caso a busca A* nÃ£o contenha um caminho direto ao
        //objetivo, ou seja, o RobÃ´ encontra-se cercado por regiÃµes
        //desconhecidas, ele deve continuar na busca cega...
        //
        // Caso exista um caminho adequado, a variÃ¡vel "escolha" Ã© setada.
    
        escolha=AEstrela(this.x, this.y, StartBot.saidaX, StartBot.saidaY);

//        escolha=TracaRota(x,y,StartBot.saidaX,StartBot.saidaY);


    }
    if (escolha>3) { //saÃ­da nÃ£o conhecida...explorar ambiente
                     //prÃ³ximo passo Ã© avaliar os caminhos que sÃ£o vÃ¡lidos,
                     //ou seja, sem muros ou outros RobÃ´s no caminho.
                     //Caminhos com muros sÃ£o registrados na memÃ³ria.
        escolha = 4;
        for (int i=0;i<4;i++){
            if (mv[i].nota>0){
                if (_world.getObjectAtX$Y(mv[i].x, mv[i].y)!=null){
                    //HÃ¡ algo no caminho - vou verificar se Ã© um muro ou
                    //outro RobÃ´
                    mv[i].nota=0; //caminho bloqueado
                    if (!_world.getObjectAtX$Y(mv[i].x, mv[i].y).toString().equalsIgnoreCase("bot")){
                        //nÃ£o Ã© um RobÃ´....vamos registrar o muro na memÃ³ria
                        _memo.putValue$atX$Y(1, mv[i].x, mv[i].y);
                    } else {
                        if (_memo.getValueAtX$Y(mv[i].x, mv[i].y)==0)
                            _memo.putValue$atX$Y(2, mv[i].x, mv[i].y);
                    }
                } else {
                    if (mv[i].x==0||mv[i].x==_world.getSizeX()-1||mv[i].y==0||mv[i].y==_world.getSizeY()-1){
                        // Encontrada a saÃ­da!
                        mv[i].nota=50;
                        _memo.putValue$atX$Y(50, mv[i].x, mv[i].y);
                        StartBot.saidaX=mv[i].x;
                        StartBot.saidaY=mv[i].y;
                    } else {
                        //NÃ£o hÃ¡ nada no caminho, vou atribuir uma nota a este caminho
                        //de acordo com as heurÃ­sticas disponÃ­veis
                        if (_memo.getValueAtX$Y(mv[i].x, mv[i].y)==0)
                            _memo.putValue$atX$Y(2, mv[i].x, mv[i].y);
                        mv[i].nota=15-_memo.getValueAtX$Y(mv[i].x, mv[i].y);
                        if (i==this.ultimoMovimento)
                            //crÃ©dito para o Ãºltimo movimento
                            mv[i].nota++;
                    }
                }
            }
            if (mv[i].nota>mv[escolha].nota) escolha=i;
        }
        if (mv[escolha].nota>0) this.ultimoMovimento=escolha;
    }
    return mv[escolha];
}

private class No implements Comparable<No>{
    public int Dist;
    public int Cust;
    public int Heu;
    public No Pai;
    public int x;
    public int y;
    public No(int x, int y){
        this.x=x;
        this.y=y;
    }
    public int compareTo(No outro){
        if (outro.Heu>this.Heu)
            return -1;
        else if (outro.Heu<this.Heu)
            return 1;
        else
            return 0;
    }
    public void setDist(int xf, int yf){
        //seta a distância de Manhatan até o ponto final
        this.Dist=Math.abs(xf-this.x)+Math.abs(yf-this.y);
    }
    public void setHeu(int xf, int yf, int custo){
        this.setDist(xf, yf);
        this.Cust=custo;
        this.Heu=this.Cust+this.Dist;
    }
    public String toString(){
        return String.format("x=%d y=%d  Custo=%d  Dist.=%d  Heurística=%d ", this.x,this.y,this.Cust,this.Dist, this.Heu);
    }
    public boolean Equall(No no){
        if (no.x==this.x && no.y==this.y)
            return true;
        else
            return false;
    }
}
public int AEstrela(int x1, int y1, int x2, int y2){

    //
    ArrayList<No> cache;
    ArrayList<No> rota;
    No no, tmp;

    //Inicializando as ArrayList
    cache=new ArrayList<No>();
    rota=new ArrayList<No>();

    int filaX[]={0, 0, 1, -1};
    int filaY[]={-1, 1, 0, 0};

    //Adiciona o nó inicial ao cache
    no=new No(x1,y1);
    no.setHeu(x2, y2, 1);
    no.Pai=null;
    cache.add(no);
    boolean cy=true;
    do {
        Collections.sort(cache);
        if (cache.size()>0){
            no=cache.get(0);
            rota.add(cache.remove(0));
        }
        if (!(no.x==x2 && no.y==y2)) {

            for (int i=0;i<4;i++){
                tmp=new No(no.x+filaX[i],no.y+filaY[i]);
                //System.out.println(tmp);
                if (tmp.x>=0 && tmp.y>=0 && tmp.x<_world.getSizeX() && tmp.y<_world.getSizeY()){
                    //coordenadas válidas...verifica existência do caminho
                    if (_memo.getValueAtX$Y(tmp.x, tmp.y)>1){
                        //Caminho disponível
                        tmp.setHeu(x2, y2, no.Cust+1);
                        tmp.Pai=no;
                        //vamos verificar se este ponto está em uma das listas
                        boolean b=true;
                        for (int j=0;j<rota.size();j++)
                            if (tmp.Equall(rota.get(j))){
                                b=false;
                                break;
                            }
                        if (b) {
                            for (int j=0;j<cache.size();j++)
                                if (tmp.Equall(cache.get(j))){
                                    b=false;
                                    break;
                                }
                        }
                        if (b)
                            cache.add(tmp);

                    }
                }
            }

        } else cy=false;
    } while (cache.size()>0&&cy);
    int tempX=x1, tempY=y1;
    if (rota.size()>0) {
        no=rota.get(rota.size()-1);
        //O primeiro nó da lista deve ser o alvo. Caso contrário, não há caminho
        //conhecido até o alvo.
        if (no.x==x2&&no.y==y2){
            while (no.Pai!=null){
                tempX=no.x;
                tempY=no.y;
                //System.out.println(no);
                no=no.Pai;
            }
        } else return 4;

        //System.out.println(no);

        if (tempY<y1) return 0;
        if (tempY>y1) return 1;
        if (tempX>x1) return 2;
        if (tempX<x1) return 3;

    
    }

    return 4;
}
public Object putAtX$Y (int inX, int inY)
{
    x = inX;
    y = inY;
    _world.putObject$atX$Y (this, x, y);
    return this;
}

public String toString ()
{
    return "bot";
}

} 
